﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.Runtime.Serialization;

namespace dasBlog.Storage
{
    [DataContract]
    public sealed class Moniker
    {
        public class Segment : ICloneable
        {
            public PathSegmentName Name { get; set; }
            public string Id { get; set; }
            public bool IsLast { get; set; }

            public Segment()
            {

            }

            public Segment(PathSegmentName name, string id, bool isLast)
            {
                Name = name;
                Id = id;
                IsLast = isLast;
            }
            public object Clone()
            {
                return new Segment(Name, Id, IsLast);
            }

        }

        public const string Scheme = "dasblog";

        private string _scope;
        public string Scope { get { return _scope; } }
        private List<Segment> _segments;
        public IList<Segment> Segments { get { return new List<Segment>(_segments); } }
        
        private Moniker()
        {
            _segments = new List<Segment>();
        }

        public Moniker(string contextIdentifier)
            :this()
        {
            Parse( new Uri( contextIdentifier ));
        }

        public Moniker(string scopeid, Collection<string> pathSegments)
            : this()
        {
            this._scope = scopeid;
            for (int i = 0; i < pathSegments.Count; i += 2)
            {
                Segment segment = new Segment();
                segment.Name = new PathSegmentName(pathSegments[i]);
                if (i + 1 < pathSegments.Count && !string.IsNullOrEmpty(pathSegments[i + 1]))
                {
                    segment.Id = pathSegments[i + 1];
                }
                if (i + 2 >= pathSegments.Count)
                {
                    segment.IsLast = true;
                }
                _segments.Add(segment);
            }
        }

        public Moniker(Uri moniker)
            : this()
        {
            Parse(moniker);
        }

        public Moniker(Moniker uri, string id)
            : this()
        {
            if (uri.ItemId != null)
                throw new ArgumentException("Not a context-id", "uri"); 

            _scope = uri.Scope;
            _segments = new List<Segment>(from Segment s in uri._segments select (Segment)s.Clone());
            _segments.Last().Id = id;            
        }

        public Moniker(Moniker uri, PathSegmentName node)
            : this()
        {
            if (uri._segments.Count > 0)
            {
                if (uri.ItemId == null)
                    throw new ArgumentException("Not an item-id", "uri");

                _scope = uri.Scope;
                _segments = new List<Segment>(from Segment s in uri._segments select (Segment)s.Clone());
                _segments.Last().IsLast = false;
                _segments.Add(new Segment { Name = node, IsLast = true });
            }
            else
            {
                _scope = uri._scope;
                _segments = new List<Segment>();
                _segments.Add(new Segment { Name = node, IsLast = true });
            }
        }

        public Moniker(Moniker uri, string id, PathSegmentName subnode):this(uri,id)
        {
            _segments.Last().IsLast = false;
            _segments.Add(new Segment { Name = subnode, IsLast = true });
        }

        public Moniker(Moniker uri, string id, PathSegmentName subnode, string subid):this(uri,id,subnode)
        {
            _segments.Last().Id = subid;
        }

        public Moniker(string scopeName, PathSegmentName node1)
            :this()
        {
            _scope = scopeName;
            _segments.Add(new Segment { Name = node1, IsLast = true });
        }

        public Moniker(string scopeName, PathSegmentName node1, string id1):this()
        {
            _scope = scopeName;
            _segments.Add(new Segment { Name = node1, Id = id1, IsLast = true });
        }

        public Moniker(string scopeName, PathSegmentName node1, string id1, PathSegmentName node2):this(scopeName,node1,id1)
        {
            _segments.Last().IsLast = false;
            _segments.Add(new Segment { Name = node2, IsLast = true });
        }

        public Moniker(string scopeName, PathSegmentName node1, string id1, PathSegmentName node2, string id2):this(scopeName,node1,id1)
        {
            _segments.Last().IsLast = false;
            _segments.Add(new Segment { Name = node2, Id=id2, IsLast = true });
        }


        private void Parse(Uri moniker)
        {
            _scope = moniker.Host;
            _segments = new List<Segment>();
            if (moniker.AbsolutePath == "/")
                return;

            string[] pathSegments = moniker.AbsolutePath.Split('/');
            int end = pathSegments[pathSegments.Length - 1] == "" ? pathSegments.Length - 1 : pathSegments.Length;
            for (int i = 1; i < end; i += 2)
            {
                if (pathSegments[i] == "")
                    continue;

                Segment segment = new Segment();
                segment.Name = new PathSegmentName(Uri.UnescapeDataString(pathSegments[i]));
                if (i + 1 < end && !string.IsNullOrEmpty(pathSegments[i+1]))
                {
                    segment.Id = Uri.UnescapeDataString(pathSegments[i+1]);
                }
                if (i + 2 >= end)
                {
                    segment.IsLast = true;
                }
                _segments.Add(segment);
            }
        }

        public Uri ToUri() 
        {
            return new Uri(ToString());
        }

        private Uri _uri = null;
        public Uri ToUri(Uri baseUri)
        {
            if (_uri == null)
            {
                string path = "./" + Scope + "/";
                for (int i = 0; i < _segments.Count; i++)
                {
                    bool bLast = i + 1 >= _segments.Count;
                    var segment = _segments[i];
                    path += segment.Name.Value + "/";
                    if (segment.Id != null)
                    {
                        path += segment.Id + "/";
                    }
                }
                _uri = new Uri(baseUri, path.Substring(0, path.Length - 1));
            }
            return _uri;
        }

        public override string ToString()
        {
            return MakeString(true);
        }

        private string _stringWithId = null;
        private string _string = null;
        private string MakeString(bool withId)
        {
            if ((withId && _stringWithId == null) ||
                (!withId && _string == null))
            {
                string path = Scheme + "://" + Scope + "/";
                for (int i = 0; i < _segments.Count; i++)
                {
                    bool bLast = i + 1 >= _segments.Count;
                    var segment = _segments[i];
                    path += segment.Name.Value + "/";
                    if (((!withId && !bLast) || withId) &&
                        segment.Id != null)
                    {
                        path += segment.Id + "/";
                    }
                }
                if (withId)
                    _stringWithId = path.Substring(0, path.Length - 1);
                else
                    _string = path.Substring(0, path.Length - 1);
            }
            
            if (withId)
                return _stringWithId;
            else
                return _string;
            
        }


        public string ItemId
        {
            get
            {
                if (_segments.Count == 0)
                    return null;

                Segment lastSegment = _segments.Last();
                return lastSegment.Id;
            }
            set
            {
                if (_segments.Count == 0)
                    throw new InvalidOperationException("Can't set Id on Uri without segments");

                Segment lastSegment = _segments.Last();
                lastSegment.Id = value;
            }
        }

        public string ItemPath
        {
            get
            {
                return MakeString(false);
            }
        }

        public override bool Equals(object obj)
        {
            return (obj).ToString() == ToString();
        }

        public override int GetHashCode()
        {
            return ToString().GetHashCode();
        }

        [DataMember]
        private string Value
        {
            get
            {
                return ToString();
            }
            set
            {
                Parse(new Uri(value));
            }
        }

        public static Moniker FromScopeName(string scopeName)
        {
            Moniker uri = new Moniker();
            uri._scope = scopeName;
            return uri;
        }
    }
}
